#include <iostream>
#include <string>

// Irrlicht and Cg Headers
#include "irrlicht.h"
#include <Cg/cg.h>

// IrrCg
#include "IrrCg.h"

// Irrlicht namespaces
using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

// Chernobyl Game Engine namespaces
using namespace IrrCg;

// Other namespaces
using namespace std;

IrrlichtDevice* Device = 0;
IVideoDriver* Driver = 0;
ISceneManager* SceneManager = 0;
stringw window = "";

std::string ShaderPath = "";

class PerVertexLightingCallBack : public ICgShaderConstantSetCallBack
{
public:
    CGparameter	WorldViewProjection,
                globalAmbient,
                lightColor,
                lightPosition,
                eyePosition,
                Ke,
                Ka,
                Kd,
                Ks,
                shininess;

	virtual void OnSetConstants(ICgServices* services,CGprogram Vertex,CGprogram Pixel,const SMaterial &Material)
	{
	    // Vertex Shader
	    WorldViewProjection = cgGetNamedParameter(Vertex, "modelViewProj");
        core::matrix4 worldViewProj;
		worldViewProj = Driver->getTransform(video::ETS_PROJECTION);
		worldViewProj *= Driver->getTransform(video::ETS_VIEW);
		worldViewProj *= Driver->getTransform(video::ETS_WORLD);
		services->setMatrix(WorldViewProjection,ICGT_MATRIX_IDENTITY,worldViewProj);

        globalAmbient = cgGetNamedParameter(Vertex, "globalAmbient");
        services->setParameter3f(globalAmbient, 0.1f, 0.1f, 0.1f);

        lightColor = cgGetNamedParameter(Vertex, "lightColor");
        services->setParameter3f(lightColor, 0.5f, 1.0f, 0.5f);

        lightPosition = cgGetNamedParameter(Vertex, "lightPosition");
        services->setParameter3f(lightPosition, 0.0f, 0.0f, 120.0f);

        eyePosition = cgGetNamedParameter(Vertex, "eyePosition");
        vector3df CameraPosition = Device->getSceneManager()->getActiveCamera()->getAbsolutePosition();
        services->setParameter3f(eyePosition, CameraPosition.X, CameraPosition.Y, CameraPosition.Z);

        Ke = cgGetNamedParameter(Vertex, "Ke");
        services->setParameter3f(Ke, 0.9f, 0.6f, 0.3f);

        Ka = cgGetNamedParameter(Vertex, "Ka");
        services->setParameter3f(Ka, 0.4f, 0.4f, 0.4f);

        Kd = cgGetNamedParameter(Vertex, "Kd");
        services->setParameter3f(Kd, 1.0f, 1.0f, 1.0f);

        Ks = cgGetNamedParameter(Vertex, "Ks");
        services->setParameter3f(Ks, 1.0f, 1.0f, 1.0f);

        shininess = cgGetNamedParameter(Vertex, "shininess");
        services->setParameter1f(shininess, 0.05f);
	}
};

class PerPixelLightingCallBack : public ICgShaderConstantSetCallBack
{
public:
    CGparameter	WorldViewProjection,
                globalAmbient,
                lightColor,
                lightPosition,
                eyePosition,
                Ke,
                Ka,
                Kd,
                Ks,
                shininess;

	virtual void OnSetConstants(ICgServices* services,CGprogram Vertex,CGprogram Pixel,const SMaterial &Material)
	{
	    // Vertex Shader
	    WorldViewProjection = cgGetNamedParameter(Vertex, "modelViewProj");
        core::matrix4 worldViewProj;
		worldViewProj = Driver->getTransform(video::ETS_PROJECTION);
		worldViewProj *= Driver->getTransform(video::ETS_VIEW);
		worldViewProj *= Driver->getTransform(video::ETS_WORLD);
		services->setMatrix(WorldViewProjection,ICGT_MATRIX_IDENTITY,worldViewProj);

        // Pixel Shader
        globalAmbient = cgGetNamedParameter(Pixel, "globalAmbient");
        services->setParameter3f(globalAmbient, 0.1f, 0.1f, 0.1f);

        lightColor = cgGetNamedParameter(Pixel, "lightColor");
        services->setParameter3f(lightColor, 0.5f, 1.0f, 0.5f);

        lightPosition = cgGetNamedParameter(Pixel, "lightPosition");
        services->setParameter3f(lightPosition, 0.0f, 0.0f, 120.0f);

        eyePosition = cgGetNamedParameter(Pixel, "eyePosition");
        vector3df CameraPosition = Device->getSceneManager()->getActiveCamera()->getAbsolutePosition();
        services->setParameter3f(eyePosition, CameraPosition.X, CameraPosition.Y, CameraPosition.Z);

        Ke = cgGetNamedParameter(Pixel, "Ke");
        services->setParameter3f(Ke, 0.9f, 0.6f, 0.3f);

        Ka = cgGetNamedParameter(Pixel, "Ka");
        services->setParameter3f(Ka, 0.4f, 0.4f, 0.4f);

        Kd = cgGetNamedParameter(Pixel, "Kd");
        services->setParameter3f(Kd, 1.0f, 1.0f, 1.0f);

        Ks = cgGetNamedParameter(Pixel, "Ks");
        services->setParameter3f(Ks, 1.0f, 1.0f, 1.0f);

        shininess = cgGetNamedParameter(Pixel, "shininess");
        services->setParameter1f(shininess, 0.05f);
	}
};

int main()
{
    printf("\n *** IrrCg Lighting Example by Patryk Nadrowski ***\n\n");

    char i;

    // Select Irrlicht Driver
    video::E_DRIVER_TYPE DriverType;
	printf("\n Please select the driver you want for this demo:\n\n"\
		"  (a) Direct3D 9.0c\n  (b) OpenGL 1.5\n"\
		"  (otherKey) exit\n\n");

	std::cin >> i;

	switch(i)
	{
		case 'a':
            DriverType = EDT_DIRECT3D9;
            window = " Direct3D9 PS: [ps_2_0] ";
            break;

		case 'b':
            DriverType = EDT_OPENGL;
            window = " OpenGL PS: [arbfp1] ";
            break;

		default: return 1;
	}

	// Select Shader Type
	printf("\n Please select the shader type:\n\n"\
		"  (a) Per Vertex Lighting (Default)\n  (b) Per Pixel Lighting\n\n");

	std::cin >> i;

	switch(i)
	{
		case 'b':
            ShaderPath = "../../cg/LightingFrag.cg";
            break;

		default:
            ShaderPath = "../../cg/LightingVert.cg";
            break;
	}

	Device = createDevice(DriverType,dimension2d<u32>(800, 600),32,false,false,false,0);
	Driver = Device->getVideoDriver();
	SceneManager = Device->getSceneManager();

    // Create Cg Programming Service
    ICgProgrammingServices* GPU = new ICgProgrammingServices(Device);

    // Create Cg Shader Material
    s32 CgLightingMaterialType = 0;

    if(ShaderPath == "../../cg/LightingVert.cg")
    {
        PerVertexLightingCallBack* VLCB = new PerVertexLightingCallBack();
        CgLightingMaterialType = GPU->addCgShaderMaterialFromFiles(CG_SOURCE,"../../cg/LightingVert.cg","main_v","arbvp1","vs_2_0","../../cg/LightingVert.cg","main_f","arbfp1","ps_2_0",VLCB,EMT_SOLID);
    }
    if(ShaderPath == "../../cg/LightingFrag.cg")
    {
        PerPixelLightingCallBack* PLCB = new PerPixelLightingCallBack();
        CgLightingMaterialType = GPU->addCgShaderMaterialFromFiles(CG_SOURCE,"../../cg/LightingFrag.cg","main_v","arbvp1","vs_2_0","../../cg/LightingFrag.cg","main_f","arbfp1","ps_2_0",PLCB,EMT_SOLID);
    }

    // Create Node
    IAnimatedMesh* mesh = SceneManager->getMesh("../../media/elephant.obj");
	ISceneNode* node = SceneManager->addAnimatedMeshSceneNode(mesh);
	node->setPosition(core::vector3df(0,0,0));
	node->setRotation(core::vector3df(0,0,0));
	node->setScale(core::vector3df(1,1,1));
	node->setAutomaticCulling(EAC_FRUSTUM_BOX);
	node->setMaterialFlag(video::EMF_LIGHTING, false);
	// Apply Cg Shader Material to Node
	node->setMaterialType((video::E_MATERIAL_TYPE)CgLightingMaterialType);

    ISceneNode* node2 = SceneManager->addCubeSceneNode(50);
	node2->setPosition(core::vector3df(0,0,-100));
	node2->setRotation(core::vector3df(0,0,0));
	node2->setScale(core::vector3df(1,1,1));
	node2->setAutomaticCulling(EAC_FRUSTUM_BOX);
	node2->setMaterialFlag(video::EMF_LIGHTING, false);
	// Apply Cg Shader Material to Node
	node2->setMaterialType((video::E_MATERIAL_TYPE)CgLightingMaterialType);

    ISceneNode* node3 = SceneManager->addCubeSceneNode(50);
	node3->setPosition(core::vector3df(0,0,100));
	node3->setRotation(core::vector3df(0,0,0));
	node3->setScale(core::vector3df(1,1,1));
	node3->setAutomaticCulling(EAC_FRUSTUM_BOX);
	node3->setMaterialFlag(video::EMF_LIGHTING, false);
	node3->setMaterialTexture(0,Driver->getTexture("../../media/checker.bmp") );

    // Create Camera
	scene::ICameraSceneNode* cam = SceneManager->addCameraSceneNodeFPS(0,100.0f,0.1f);
	cam->setPosition(core::vector3df(120,0,0));
	cam->setTarget(core::vector3df(0,0,0));

    // Hide Mouse Cursor
    Device->getCursorControl()->setVisible(false);

    // Last FPS
    int lastFPS = -1;

	while(Device->run())
    if (Device->isWindowActive())
	{
	    // Begin Scene
		Driver->beginScene(true, true, video::SColor(255,0,0,0));

        // Draw All
        SceneManager->drawAll();

        // End Scene
		Driver->endScene();

		int fps = Driver->getFPS();
		int poly = Driver->getPrimitiveCountDrawn();

		if (lastFPS != fps)
		{
		    core::stringw str = "";
            str = L"Irrlicht Cg - Per Vertex and Per Pixel Lighting for";
            str += window;
            str += L" FPS: ";
            str += fps;
            str += L" Poly: ";
            str += poly;

            Device->setWindowCaption(str.c_str());
            lastFPS = fps;
		}
	}
    delete GPU;
	Device->drop();
	return 0;
}
